1use crate::copp::copp2::formulation::Topp2Problem;
21use crate::copp::{ApproxOrdering, approx_order};
22use crate::diag::{
23 CoppError, DebugVerboser, SilentVerboser, SummaryVerboser, TraceVerboser, Verboser, Verbosity,
24 check_abs_rel_tol, check_strictly_positive, format_duration_human,
25};
26use crate::math::numerical::{
27 LP_BOUND, Lp2dWarmStart, LpToleranceOptions, lp_1d, lp_2d_incre_max_y, normalize_lp2d,
28};
29use core::f64;
30
31pub struct ReachSet2 {
34 pub a_max: Vec<f64>,
35 pub a_min: Vec<f64>,
36}
37
38#[inline]
55pub fn reach_set2_backward(
56 problem: &Topp2Problem,
57 options: &ReachSet2Options,
58) -> Result<ReachSet2, CoppError> {
59 match options.verbosity {
60 Verbosity::Silent => reach_set2_core::<false>(problem, (options, SilentVerboser)),
61 Verbosity::Summary => reach_set2_core::<false>(problem, (options, SummaryVerboser::new())),
62 Verbosity::Debug => reach_set2_core::<false>(problem, (options, DebugVerboser::new())),
63 Verbosity::Trace => reach_set2_core::<false>(problem, (options, TraceVerboser::new())),
64 }
65}
66
67#[inline]
83pub fn reach_set2_bidirectional(
84 problem: &Topp2Problem,
85 options: &ReachSet2Options,
86) -> Result<ReachSet2, CoppError> {
87 match options.verbosity {
88 Verbosity::Silent => reach_set2_core::<true>(problem, (options, SilentVerboser)),
89 Verbosity::Summary => reach_set2_core::<true>(problem, (options, SummaryVerboser::new())),
90 Verbosity::Debug => reach_set2_core::<true>(problem, (options, DebugVerboser::new())),
91 Verbosity::Trace => reach_set2_core::<true>(problem, (options, TraceVerboser::new())),
92 }
93}
94
95fn reach_set2_core<const BIDIRECTION: bool>(
100 problem: &Topp2Problem,
101 options_verboser: (&ReachSet2Options, impl Verboser),
102) -> Result<ReachSet2, CoppError> {
103 let (options, mut verboser) = options_verboser;
104 if verboser.is_enabled(Verbosity::Summary) {
105 verboser.record_start_time();
106 crate::verbosity_log!(
107 crate::diag::Verbosity::Summary,
108 "reach_set2 started: {} <= idx_s <= {}, a_start = {}, a_final = {}.",
109 problem.idx_s_interval.0,
110 problem.idx_s_interval.1,
111 problem.a_boundary.0,
112 problem.a_boundary.1,
113 );
114 if BIDIRECTION {
115 crate::verbosity_log!(
116 crate::diag::Verbosity::Summary,
117 "Bidirectional reachable set will be computed."
118 );
119 } else {
120 crate::verbosity_log!(
121 crate::diag::Verbosity::Summary,
122 "Backward reachable set will be computed."
123 );
124 }
125 }
126
127 let (idx_s_start, idx_s_final) = problem.idx_s_interval;
128 let a_max_0 = problem.constraints.amax_unchecked(problem.idx_s_interval.0);
129 if matches!(
130 approx_order(
131 problem.a_boundary.0,
132 a_max_0,
133 options.a_cmp_abs_tol,
134 options.a_cmp_rel_tol,
135 ),
136 ApproxOrdering::Greater
137 ) {
138 let err = CoppError::Infeasible(
139 "reach_set2".into(),
140 format!(
141 "The initial state a_start = {} cannot be greater than the maximum feasible state a_max[0] = {a_max_0} at idx_s_start = {idx_s_start}",
142 problem.a_boundary.0,
143 ),
144 );
145 if verboser.is_enabled(Verbosity::Debug) {
146 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
147 } else if verboser.is_enabled(Verbosity::Summary) {
148 crate::verbosity_log!(
149 crate::diag::Verbosity::Debug,
150 "reach_set2: the backward pass failed at index {idx_s_start} due to infeasibility of the initial state."
151 );
152 }
153 return Err(err);
154 }
155 let a_max_f = problem.constraints.amax_unchecked(problem.idx_s_interval.1);
156 if matches!(
157 approx_order(
158 problem.a_boundary.1,
159 a_max_f,
160 options.a_cmp_abs_tol,
161 options.a_cmp_rel_tol,
162 ),
163 ApproxOrdering::Greater
164 ) {
165 let err = CoppError::Infeasible(
166 "reach_set2".into(),
167 format!(
168 "The final state a_final = {} cannot be greater than the maximum feasible state a_max[n] = {a_max_f} at idx_s_final = {idx_s_final}",
169 problem.a_boundary.1,
170 ),
171 );
172 if verboser.is_enabled(Verbosity::Debug) {
173 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
174 } else if verboser.is_enabled(Verbosity::Summary) {
175 crate::verbosity_log!(
176 crate::diag::Verbosity::Debug,
177 "reach_set2: the backward pass failed at index {idx_s_final} due to infeasibility of the final state."
178 );
179 }
180 return Err(err);
181 }
182
183 let n = idx_s_final - idx_s_start;
185 let mut a_max = vec![f64::INFINITY; n + 1];
186 let mut a_min = vec![0.0; n + 1];
187 *a_max.last_mut().unwrap() = problem.a_boundary.1;
188 *a_min.last_mut().unwrap() = problem.a_boundary.1;
189 if verboser.is_enabled(Verbosity::Debug) {
191 crate::verbosity_log!(crate::diag::Verbosity::Summary, "Backward pass started.");
192 }
193
194 let mut a_b = Vec::<(f64, f64, f64)>::with_capacity(2 + 2 * problem.constraints.acc_rows());
195 let mut a_max_next = problem.a_boundary.1;
196 let mut a_min_next = problem.a_boundary.1;
197 for (k, (a_max_k, a_min_k)) in a_max
198 .iter_mut()
199 .zip(a_min.iter_mut())
200 .take(n)
201 .enumerate()
202 .rev()
203 {
204 let idx_s = idx_s_start + k;
205 if verboser.is_enabled(Verbosity::Trace) {
206 crate::verbosity_log!(
207 crate::diag::Verbosity::Summary,
208 "\tBackward pass at k = {k} (idx_s = {idx_s}) to compute a[k]: {a_min_next} <= a[k+1] <= {a_max_next}."
209 );
210 }
211
212 a_b.clear();
213 a_b.push((1.0, 0.0, a_max_next));
214 a_b.push((-1.0, 0.0, -a_min_next));
215 problem.constraints.fill_acc_topp2::<true>(&mut a_b, idx_s);
216 let a_next_mid = 0.5 * (a_max_next + a_min_next);
219 let (a_max_curr, a_min_curr) = match approx_order(
220 a_max_next,
221 a_min_next,
222 options.a_cmp_abs_tol,
223 options.a_cmp_rel_tol,
224 ) {
225 ApproxOrdering::Equal => {
226 if verboser.is_enabled(Verbosity::Trace) {
227 crate::verbosity_log!(
228 crate::diag::Verbosity::Summary,
229 "\t\ta[k+1] = {a_next_mid}"
230 );
231 }
232 lp_1d::<true>(
233 a_b.iter().skip(2).map(|&coeffs| {
234 (coeffs.1, coeffs.2 - coeffs.0 * a_next_mid)
237 }),
238 &LpToleranceOptions::with_feas_tol(options.lp_feas_tol),
239 )
240 }
241 ApproxOrdering::Greater => {
242 if verboser.is_enabled(Verbosity::Trace) {
243 crate::verbosity_log!(
244 crate::diag::Verbosity::Summary,
245 "\t\t{a_min_next} <= a[k+1] <= {a_max_next}"
246 );
247 }
248 let (a_test_max, a_test_min) = lp_1d::<true>(
250 a_b.iter().skip(2).map(|&coeffs| {
251 (coeffs.0, coeffs.2 - coeffs.1 * *a_min_k)
254 }),
255 &LpToleranceOptions::with_feas_tol(options.lp_feas_tol),
256 );
257 let flag_need_min = a_test_max.is_nan()
258 || a_test_min.is_nan()
259 || a_test_max < a_min_next
260 || a_test_max > a_max_next;
261 if verboser.is_enabled(Verbosity::Trace) {
262 crate::verbosity_log!(
263 crate::diag::Verbosity::Summary,
264 "\t\tBackward skip checks at idx_s = {idx_s}: need_max = true, need_min = {flag_need_min}."
265 );
266 }
267 if flag_need_min {
268 backward_bound_a_next::<true, true>(&mut a_b, a_next_mid, options.lp_feas_tol)
269 } else {
270 (
271 backward_bound_a_next::<true, false>(
272 &mut a_b,
273 a_next_mid,
274 options.lp_feas_tol,
275 )
276 .0,
277 *a_min_k,
278 )
279 }
280 }
281 ApproxOrdering::Less => {
282 let err = CoppError::Infeasible(
283 "reach_set2".into(),
284 format!(
285 "The reachable set is empty at index {idx_s} during the backward pass where a_max_next = {a_max_next}, a_min_next = {a_min_next}"
286 ),
287 );
288 if verboser.is_enabled(Verbosity::Debug) {
289 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
290 } else if verboser.is_enabled(Verbosity::Summary) {
291 crate::verbosity_log!(
292 crate::diag::Verbosity::Debug,
293 "reach_set2: the backward pass failed at index {idx_s} due to infeasibility."
294 );
295 }
296 return Err(err);
297 }
298 };
299
300 if a_max_curr.is_nan() || a_min_curr.is_nan() {
301 let err = CoppError::Infeasible(
302 "reach_set2".into(),
303 format!(
304 "The reachable set is empty at index {idx_s} during the backward pass where a_max = {a_max_curr}, a_min = {a_min_curr}"
305 ),
306 );
307 if verboser.is_enabled(Verbosity::Debug) {
308 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
309 } else if verboser.is_enabled(Verbosity::Summary) {
310 crate::verbosity_log!(
311 crate::diag::Verbosity::Debug,
312 "reach_set2: the backward pass failed at index {idx_s} due to infeasibility."
313 );
314 }
315 return Err(err);
316 }
317 if verboser.is_enabled(Verbosity::Trace) {
318 crate::verbosity_log!(
319 crate::diag::Verbosity::Summary,
320 "\t\tBackward LP result at k = {k}: {a_min_curr} <= a[k] <= {a_max_curr}."
321 );
322 }
323
324 *a_max_k = a_max_curr.min(problem.constraints.amax_unchecked(idx_s));
325 *a_min_k = a_min_curr.max(0.0);
326
327 if verboser.is_enabled(Verbosity::Trace) {
328 crate::verbosity_log!(
329 crate::diag::Verbosity::Summary,
330 "\t\tAfter clipping with path constraints at k = {k}: {} <= a[k] <= {}.",
331 *a_min_k,
332 *a_max_k
333 );
334 }
335
336 match approx_order(
337 *a_max_k,
338 *a_min_k,
339 options.a_cmp_abs_tol,
340 options.a_cmp_rel_tol,
341 ) {
342 ApproxOrdering::Less => {
343 let err = CoppError::Infeasible(
344 "reach_set2".into(),
345 format!(
346 "The reachable set is empty at k = {k} (idx_s = {idx_s}) during the backward pass where a_max = {a_max_k}, a_min = {a_min_k}"
347 ),
348 );
349 if verboser.is_enabled(Verbosity::Debug) {
350 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
351 } else if verboser.is_enabled(Verbosity::Summary) {
352 crate::verbosity_log!(
353 crate::diag::Verbosity::Debug,
354 "reach_set2: the backward pass failed at k = {k} (idx_s = {idx_s}) due to infeasibility."
355 );
356 }
357 return Err(err);
358 }
359 ApproxOrdering::Equal => {
360 if verboser.is_enabled(Verbosity::Debug) && k < n - 1 {
361 crate::verbosity_log!(
362 crate::diag::Verbosity::Summary,
363 "The backward reachable set at k = {k} (idx_s = {idx_s}) is degenerate since a_max = {a_max_k} and a_min = {a_min_k} are approximately equal."
364 );
365 }
366 *a_max_k = 0.5 * (*a_max_k + *a_min_k);
367 *a_min_k = *a_max_k;
368 }
369 ApproxOrdering::Greater => {}
370 }
371 a_max_next = *a_max_k;
372 a_min_next = *a_min_k;
373
374 if verboser.is_enabled(Verbosity::Trace) {
375 crate::verbosity_log!(
376 crate::diag::Verbosity::Summary,
377 "\t\tBackward propagated interval to k-1: {a_min_next} <= a[k] <= {a_max_next}."
378 );
379 }
380 }
381
382 if BIDIRECTION {
383 if verboser.is_enabled(Verbosity::Debug) {
384 crate::verbosity_log!(crate::diag::Verbosity::Summary, "Forward pass started.");
385 }
386
387 *a_max.first_mut().unwrap() = problem.a_boundary.0;
388 *a_min.first_mut().unwrap() = problem.a_boundary.0;
389 let mut a_max_prev = problem.a_boundary.0;
390 let mut a_min_prev = problem.a_boundary.0;
391 for (k, (a_max_k, a_min_k)) in a_max.iter_mut().zip(a_min.iter_mut()).enumerate().skip(1) {
392 let idx_s = idx_s_start + k;
393 if verboser.is_enabled(Verbosity::Trace) {
394 crate::verbosity_log!(
395 crate::diag::Verbosity::Summary,
396 "\tForward pass at k = {k} (idx_s = {idx_s}): prev interval {} <= a[k-1] <= {}, backward interval {} <= a[k] <= {}.",
397 a_min_prev,
398 a_max_prev,
399 *a_min_k,
400 *a_max_k
401 );
402 }
403
404 a_b.clear();
405 a_b.push((1.0, 0.0, a_max_prev));
406 a_b.push((-1.0, 0.0, -a_min_prev));
407 problem
408 .constraints
409 .fill_acc_topp2::<false>(&mut a_b, idx_s_start + k - 1);
410 let a_prev_mid = 0.5 * (a_max_prev + a_min_prev);
413 let (a_max_curr, a_min_curr) = match approx_order(
414 a_max_prev,
415 a_min_prev,
416 options.a_cmp_abs_tol,
417 options.a_cmp_rel_tol,
418 ) {
419 ApproxOrdering::Equal => {
420 lp_1d::<true>(
421 a_b.iter().skip(2).map(|&coeffs| {
422 (coeffs.1, coeffs.2 - coeffs.0 * a_prev_mid)
425 }),
426 &LpToleranceOptions::with_feas_tol(options.lp_feas_tol),
427 )
428 }
429 ApproxOrdering::Greater => {
430 let (a_test_max, a_test_min) = lp_1d::<true>(
432 a_b.iter().skip(2).map(|&coeffs| {
433 (coeffs.0, coeffs.2 - coeffs.1 * *a_max_k)
436 }),
437 &LpToleranceOptions::with_feas_tol(options.lp_feas_tol),
438 );
439 let flag_need_max = a_test_max.is_nan()
440 || a_test_min.is_nan()
441 || a_test_max < a_min_prev
442 || a_test_max > a_max_prev;
443 let (a_test_max, a_test_min) = lp_1d::<true>(
445 a_b.iter().skip(2).map(|&coeffs| {
446 (coeffs.0, coeffs.2 - coeffs.1 * *a_min_k)
449 }),
450 &LpToleranceOptions::with_feas_tol(options.lp_feas_tol),
451 );
452 let flag_need_min = a_test_max.is_nan()
453 || a_test_min.is_nan()
454 || a_test_max < a_min_prev
455 || a_test_max > a_max_prev;
456
457 if verboser.is_enabled(Verbosity::Trace) {
458 crate::verbosity_log!(
459 crate::diag::Verbosity::Summary,
460 "\t\tForward skip checks at idx_s = {idx_s}: need_max = {flag_need_max}, need_min = {flag_need_min}."
461 );
462 }
463
464 match (flag_need_max, flag_need_min) {
466 (true, true) => backward_bound_a_next::<true, true>(
467 &mut a_b,
468 a_prev_mid,
469 options.lp_feas_tol,
470 ),
471 (true, false) => (
472 backward_bound_a_next::<true, false>(
473 &mut a_b,
474 a_prev_mid,
475 options.lp_feas_tol,
476 )
477 .0,
478 *a_min_k,
479 ),
480 (false, true) => (
481 *a_max_k,
482 backward_bound_a_next::<false, true>(
483 &mut a_b,
484 a_prev_mid,
485 options.lp_feas_tol,
486 )
487 .1,
488 ),
489 (false, false) => (*a_max_k, *a_min_k),
490 }
491 }
492 ApproxOrdering::Less => {
493 let err = CoppError::Infeasible(
494 "reach_set2".into(),
495 format!(
496 "The reachable set is empty at index {idx_s} during the forward pass where a_max_prev = {a_max_prev}, a_min_prev = {a_min_prev}"
497 ),
498 );
499 if verboser.is_enabled(Verbosity::Debug) {
500 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
501 } else if verboser.is_enabled(Verbosity::Summary) {
502 crate::verbosity_log!(
503 crate::diag::Verbosity::Debug,
504 "reach_set2: the forward pass failed at index {idx_s} due to infeasibility."
505 );
506 }
507 return Err(err);
508 }
509 };
510
511 if verboser.is_enabled(Verbosity::Trace) {
512 crate::verbosity_log!(
513 crate::diag::Verbosity::Summary,
514 "\t\tForward LP result at idx_s = {idx_s}: {a_min_curr} <= a[k] <= {a_max_curr}."
515 );
516 }
517
518 if a_max_curr.is_nan() || a_min_curr.is_nan() {
519 let err = CoppError::Infeasible(
520 "reach_set2".into(),
521 format!(
522 "The reachable set is empty at index {idx_s} during the forward pass where a_max = {a_max_curr}, a_min = {a_min_curr}"
523 ),
524 );
525 if verboser.is_enabled(Verbosity::Debug) {
526 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
527 } else if verboser.is_enabled(Verbosity::Summary) {
528 crate::verbosity_log!(
529 crate::diag::Verbosity::Debug,
530 "reach_set2: the forward pass failed at index {idx_s} due to infeasibility."
531 );
532 }
533 return Err(err);
534 }
535
536 a_max_prev = a_max_curr.min(*a_max_k);
537 a_min_prev = a_min_curr.max(*a_min_k);
538 if verboser.is_enabled(Verbosity::Trace) {
539 crate::verbosity_log!(
540 crate::diag::Verbosity::Summary,
541 "\t\tAfter intersecting with backward interval at idx_s = {idx_s}: {a_min_prev} <= a[k] <= {a_max_prev}."
542 );
543 }
544 match approx_order(
545 a_max_prev,
546 a_min_prev,
547 options.a_cmp_abs_tol,
548 options.a_cmp_rel_tol,
549 ) {
550 ApproxOrdering::Less => {
551 let err = CoppError::Infeasible(
552 "reach_set2".into(),
553 format!(
554 "The reachable set is empty at index {idx_s} during the forward pass where a_max = {a_max_prev}, a_min = {a_min_prev}"
555 ),
556 );
557 if verboser.is_enabled(Verbosity::Debug) {
558 crate::verbosity_log!(crate::diag::Verbosity::Summary, "{err:?}");
559 } else if verboser.is_enabled(Verbosity::Summary) {
560 crate::verbosity_log!(
561 crate::diag::Verbosity::Debug,
562 "reach_set2: the forward pass failed at index {idx_s} due to infeasibility."
563 );
564 }
565 return Err(err);
566 }
567 ApproxOrdering::Equal => {
568 if verboser.is_enabled(Verbosity::Debug) && k < n - 1 {
569 crate::verbosity_log!(
570 crate::diag::Verbosity::Summary,
571 "The forward reachable set at idx_s = {idx_s} is degenerate since a_max_prev and a_min_prev are approximately equal at {a_prev_mid}."
572 );
573 }
574 a_max_prev = 0.5 * (a_max_prev + a_min_prev);
575 a_min_prev = a_max_prev;
576 }
577 ApproxOrdering::Greater => {}
578 }
579 if verboser.is_enabled(Verbosity::Trace) {
580 crate::verbosity_log!(
581 crate::diag::Verbosity::Summary,
582 "\t\tForward propagated interval to next step: {a_min_prev} <= a[k] <= {a_max_prev}."
583 );
584 }
585 *a_max_k = a_max_prev;
586 *a_min_k = a_min_prev;
587 }
588 }
589
590 if verboser.is_enabled(Verbosity::Summary) {
591 crate::verbosity_log!(
592 crate::diag::Verbosity::Summary,
593 "reach_set2: {}backward total elapsed time = {}.",
594 if BIDIRECTION { "forward + " } else { "" },
595 format_duration_human(verboser.elapsed())
596 );
597 }
598
599 Ok(ReachSet2 { a_max, a_min })
600}
601
602fn backward_bound_a_next<const MAX: bool, const MIN: bool>(
606 a_b: &mut [(f64, f64, f64)],
607 a_next_mid: f64,
608 lp_fea_tol: f64,
609) -> (f64, f64) {
610 let warm_start = Lp2dWarmStart {
611 x0: (a_next_mid, LP_BOUND),
612 skip: 2,
613 };
614
615 normalize_lp2d(a_b);
616 let a_curr_max = if MAX {
617 let (_, a_curr_max) = lp_2d_incre_max_y::<_, false>(
618 a_b,
619 &warm_start,
620 &LpToleranceOptions::with_feas_tol(lp_fea_tol),
621 );
622 a_curr_max
623 } else {
624 f64::INFINITY
625 };
626
627 let a_curr_min = if MIN {
628 a_b.iter_mut().for_each(|(_, b, _)| {
630 *b = -*b;
631 });
632 let (_, a_curr_min_neg) = lp_2d_incre_max_y::<_, false>(
633 a_b,
634 &warm_start,
635 &LpToleranceOptions::with_feas_tol(lp_fea_tol),
636 );
637 -(a_curr_min_neg.min(0.0))
638 } else {
639 0.0
640 };
641
642 (a_curr_max, a_curr_min)
643}
644
645pub struct ReachSet2OptionsBuilder {
647 pub lp_feas_tol: f64,
649 pub a_cmp_abs_tol: f64,
651 pub a_cmp_rel_tol: f64,
653 pub verbosity: Verbosity,
655}
656
657impl Default for ReachSet2OptionsBuilder {
658 #[inline]
659 fn default() -> Self {
660 Self {
661 lp_feas_tol: 1e-8,
662 a_cmp_abs_tol: 1e-8,
663 a_cmp_rel_tol: 1e-8,
664 verbosity: Verbosity::default(),
665 }
666 }
667}
668
669impl ReachSet2OptionsBuilder {
670 pub fn new() -> Self {
672 Default::default()
673 }
674
675 pub fn lp_feas_tol(mut self, tol: f64) -> Self {
678 self.lp_feas_tol = tol;
679 self
680 }
681
682 #[inline]
690 pub fn a_cmp_abs_tol(mut self, tol: f64) -> Self {
691 self.a_cmp_abs_tol = tol;
692 self
693 }
694
695 #[inline]
698 pub fn a_cmp_rel_tol(mut self, tol: f64) -> Self {
699 self.a_cmp_rel_tol = tol;
700 self
701 }
702
703 #[inline]
706 pub fn verbosity(mut self, verbosity: Verbosity) -> Self {
707 self.verbosity = verbosity;
708 self
709 }
710
711 #[inline]
713 pub fn build(self) -> Result<ReachSet2Options, CoppError> {
714 self.validate()?;
715 Ok(ReachSet2Options {
716 lp_feas_tol: self.lp_feas_tol,
717 a_cmp_abs_tol: self.a_cmp_abs_tol,
718 a_cmp_rel_tol: self.a_cmp_rel_tol,
719 verbosity: self.verbosity,
720 })
721 }
722
723 #[inline]
725 pub fn validate(&self) -> Result<(), CoppError> {
726 check_strictly_positive("ReachSet2OptionsBuilder", "lp_feas_tol", self.lp_feas_tol)?;
727 check_abs_rel_tol(
728 "ReachSet2OptionsBuilder",
729 "a_cmp_abs_tol",
730 self.a_cmp_abs_tol,
731 "a_cmp_rel_tol",
732 self.a_cmp_rel_tol,
733 )?;
734 Ok(())
735 }
736}
737
738pub struct ReachSet2Options {
740 pub(crate) lp_feas_tol: f64,
741 pub(crate) a_cmp_abs_tol: f64,
742 pub(crate) a_cmp_rel_tol: f64,
743 pub(crate) verbosity: Verbosity,
744}
745
746impl ReachSet2Options {
747 #[inline]
748 pub fn lp_feas_tol(&self) -> f64 {
749 self.lp_feas_tol
750 }
751 #[inline]
752 pub fn a_cmp_abs_tol(&self) -> f64 {
753 self.a_cmp_abs_tol
754 }
755 #[inline]
756 pub fn a_cmp_rel_tol(&self) -> f64 {
757 self.a_cmp_rel_tol
758 }
759 #[inline]
760 pub fn verbosity(&self) -> Verbosity {
761 self.verbosity
762 }
763}
764
765#[cfg(test)]
766mod tests {
767 use super::*;
768 use crate::copp::copp2::stable::basic::Topp2ProblemBuilder;
769 use crate::copp::copp2::stable::topp2_ra::topp2_ra;
770 use crate::path::{add_symmetric_axial_limits_for_test, lissajous_path_for_test};
771 use crate::robot::robot_core::Robot;
772
773 #[test]
774 fn test_reach_set2() -> Result<(), CoppError> {
775 let dim = 7;
776 let n: usize = 1000;
777
778 let options = ReachSet2OptionsBuilder::new()
779 .lp_feas_tol(1E-9)
780 .a_cmp_abs_tol(1E-9)
781 .a_cmp_rel_tol(1E-9)
782 .verbosity(Verbosity::Summary)
783 .build()?;
784
785 let mut robot = Robot::with_capacity(dim, n);
786 let mut rng = rand::rng();
787
788 let (s, derivs, _, _) = lissajous_path_for_test(dim, n, &mut rng).map_err(|e| {
789 CoppError::InvalidInput(
790 "test_reach_set2_with_path_helper".into(),
791 format!("failed to generate test path derivatives: {e}"),
792 )
793 })?;
794
795 let dq = derivs.dq.as_ref().ok_or_else(|| {
796 CoppError::InvalidInput(
797 "test_reach_set2_with_path_helper".into(),
798 "missing dq in path derivatives".into(),
799 )
800 })?;
801 let ddq = derivs.ddq.as_ref().ok_or_else(|| {
802 CoppError::InvalidInput(
803 "test_reach_set2_with_path_helper".into(),
804 "missing ddq in path derivatives".into(),
805 )
806 })?;
807 let dddq = derivs.dddq.as_ref().ok_or_else(|| {
808 CoppError::InvalidInput(
809 "test_reach_set2_with_path_helper".into(),
810 "missing dddq in path derivatives".into(),
811 )
812 })?;
813
814 robot.with_s(&s.as_view())?;
815 robot.with_q(
816 &derivs.q.as_view(),
817 &dq.as_view(),
818 &ddq.as_view(),
819 Some(&dddq.as_view()),
820 0,
821 )?;
822
823 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, Some(5.0)).map_err(|e| {
824 CoppError::InvalidInput(
825 "test_reach_set2_with_path_helper".into(),
826 format!("failed to add symmetric axial limits: {e}"),
827 )
828 })?;
829
830 let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
831
832 let reach_set_back = reach_set2_backward(&topp2_problem, &options)?;
833 let reach_set_for = reach_set2_bidirectional(&topp2_problem, &options)?;
834 let a_ra = topp2_ra(&topp2_problem, &options)?;
835
836 crate::verbosity_log!(
837 crate::diag::Verbosity::Summary,
838 "a_max_back.len() = {};",
839 reach_set_back.a_max.len()
840 );
841 crate::verbosity_log!(
842 crate::diag::Verbosity::Summary,
843 "a_max_for.len() = {};",
844 reach_set_for.a_max.len()
845 );
846 crate::verbosity_log!(
847 crate::diag::Verbosity::Summary,
848 "a_ra.len() = {};",
849 a_ra.len()
850 );
851
852 Ok(())
853 }
854}